package org.jederik.studip.android.conn;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.Iterator;

import org.jederik.android.provider.StudIP.Messages;
import org.jederik.studip.android.conn.exceptions.ProtocolException;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.util.Log;
import au.com.bytecode.opencsv.CSVReader;
import au.com.bytecode.opencsv.CSVWriter;

/**
 * Stellt Methoden zur Verfügung, um die Daten von {@code ContentValues}- 
 * bzw. {@code Cursor}-Objekten ins CSV-Format zu konvertieren und umgekehrt.
 * Beim deserialisieren wird automatisch die nicht verwendete aber vom Content 
 * Provider Framework geforderte {@code _id}-Spalte eingefügt.
 * @author Jann-Frederik Laß
 *
 */
public class CSVMarshaller {

	private static final String TAG = "CSVMarshaller";

	
	// Name der eingefügten _id-Spalte:
	private static final String _ID_HEADER = "_id";
	
	// Standartwert für die eingefügte _id-Spalte:
	private static final String _ID_DEFAULT_VALUE = "0";

	/**
	 * Liest CSV-Daten ein und erzeugt aus ihnen ein {@code Cursor}-Objekt.
	 * @param in Stream mit CSV-Daten
	 * @return Cursor-Objekt, welches die eingelesenen Daten enthält.
	 * @throws IOException Wenn ein Fehler beim Lesen des Streams oder ein
	 * Protokollfehler auftritt.
	 */
	public static Cursor unmarshall(InputStream in) throws IOException {
		
		// Neuen CSVReader erzeugen:
		CSVReader reader = new CSVReader(new InputStreamReader(in));

		// Erste Zeile als Überschriften verwenden:
		MatrixCursor cursor = new MatrixCursor(insertIdColumn(reader.readNext(), true));

		try {
			String[] line;
			
			// Alle Zeilen nacheinander einlesen:
			while ((line = reader.readNext()) != null) {
				
				// ...und in Cursor einfügen:
				cursor.addRow(insertIdColumn(line));
			}
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			throw new IOException(e.getMessage());
		}
		
		return cursor;

	}
	
	/**
	 * Serialisiert ein Cursor-Objekt ins CSV-Format.
	 * @param cursor zu serialisierendes Objekt
	 * @return Zeichenkette mit CSV-Daten
	 */
	public static String marshall(Cursor cursor) {
		
		// Werte per CSVWriter ins CSV-Format bringen:
		StringWriter sw = new StringWriter();
        CSVWriter writer = new CSVWriter(sw);
        
        // Spaltenüberschriften in die erste Zeile schreiben:
        writer.writeNext(cursor.getColumnNames());
        
        String[] values = new String[cursor.getColumnCount()];
        
        // Cursor-Einträge durchlaufen:
        while (cursor.moveToNext()) {

        	// Werte in Array zusammenfassen:
        	for (int j=0; j<values.length; j++)
        		values[j] = cursor.getString(j);
        	
        	// Datensatz als Tabellenzeile ausgeben:
        	writer.writeNext(values);

        }
        
        return sw.toString();
        
	}

	/**
	 * Serialisiert eine Menge von {@code ContentValues}-Objekten als CSV-String.
	 * @param headers Spaltenüberschriften
	 * @param data Array von ContentValues'
	 * @return String mit CSV-Daten
	 */
	public static String marshall(String[] headers, ContentValues[] data) {
		
		// Werte per CSVWriter ins CSV-Format bringen:
		StringWriter sw = new StringWriter();
        CSVWriter writer = new CSVWriter(sw);
        
        // Spaltenüberschriften in die erste Zeile schreiben:
        writer.writeNext(headers);
        
        String[] values = new String[headers.length];

        for (int i=0; i<data.length; i++) {

        	// Werte in Array zusammenfassen:
        	for (int j=0; j<headers.length; j++)
        		values[j] = data[i].getAsString(headers[j]);

        	// Datensatz als Tabellenzeile ausgeben:
        	writer.writeNext(values);

        }
        
        return sw.toString();
        
	}

	// Hilfsmethode, die die vom ContentProvider-Framework geforderte _ID-Spalte
	// in einen Datensatz einfügt. Da die _ID vom Typ long sein muss, in der Stud.IP-Datenbank
	// aber String-IDs verwendet werden, benutze ich diese Spalte nicht und füge 
	// standartmäßig eine 0 ein:
	private static String[] insertIdColumn(String[] line, boolean isHeader) {
		String[] result = new String[line.length+1];
		result[0] = isHeader ? _ID_HEADER : _ID_DEFAULT_VALUE;
		for (int i=0; i<line.length; i++)
			result[i+1] = line[i];
		return result;
	}
	
	private static String[] insertIdColumn(String[] line) {
		return insertIdColumn(line, false);
	}

	/**
	 * Deserialiert CSV-Daten zu {@code ContentValues}-Objekten. Die erste Zeile
	 * wird hierbei als Überschriftenzeile angesehen.
	 * @param in CSV-Stream
	 * @return Iterator mit einem {@code ContentValues}-Objekt pro Zeile
	 */
	public static Iterator<ContentValues> unmarshallAsContentValues(
			final InputStream in) {

		try {
			return new Iterator<ContentValues>() {

				// Neuen CSVReader instanziieren:
				private CSVReader reader = new CSVReader(new InputStreamReader(in));
				
				// Überschriften einlesen:
				private String[] headers = reader.readNext();
				
				// Erste Zeile schon einlesen:
				private String[] line = reader.readNext();


				@Override
				public boolean hasNext() {
					// Konnte eine Zeile eingelesen werden?
					return line != null;
				}

				@Override
				public ContentValues next() {

					// Gibt es eine nächste Zeile?
					if (line == null) return null;

					// ContentValues-Objekt erzeugen:
					ContentValues values = new ContentValues(headers.length);
					
					// Alle Werte der Zeile unter entsprechendem Schlüssel aus 
					// der Überschriftenzeile ablegen:
					for (int i=0; i<headers.length; i++)
						values.put(headers[i], line[i]);

					try {
						// Nächste Zeile schon einlesen:
						line = reader.readNext();
					} catch (IOException e) {
						e.printStackTrace();
					}

					return values;

				}

				@Override
				public void remove() {
					try {
						// Einfach zur nächsten Zeile übergehen:
						line = reader.readNext();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			};
		} catch (IOException e) {
			throw new ProtocolException(e);
		}

	}
}
